/*
 * This file is part of Cadmium.
 * Copyright (C) 2007-2010 Xavier Clerc.
 *
 * Cadmium is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * Cadmium is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package fr.x9c.cadmium.support;

import java.awt.BorderLayout;
import java.io.File;
import java.io.InputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.io.PrintStream;
import java.net.URL;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicReference;

import javax.swing.JLabel;
import javax.swing.SwingConstants;

import fr.x9c.cadmium.gui.InOutPanel;
import fr.x9c.cadmium.kernel.ByteCodeParameters;
import fr.x9c.cadmium.kernel.FileHook;
import fr.x9c.cadmium.kernel.Interpreter;
import fr.x9c.cadmium.util.RandomAccessInputStream;

/**
 * This class implements an applet that run the OCaml toplevel executable,
 * interaction with the toplevel being done using a UI. The complete path
 * of the toplevel executable binary may be given by the applet parameter
 * named <tt>&quot;TOPLEVEL&quot;</tt>
 * (otherwise <tt>"/usr/local/bin/ocaml"</tt> is used). This path should be a
 * resource path for a resource to be loaded by this class.
 *
 * @author <a href="mailto:cadmium@x9c.fr">Xavier Clerc</a>
 * @version 1.1
 * @since 1.0
 */
public final class TopLevelApplet extends java.applet.Applet {

    /** Serialization UID. */
    static final long serialVersionUID = -3803107667818776473L;

    /** Applet info message. */
    private static final String APPLET_INFO = "OCaml toplevel applet";

    /** Applet parameter (each element is a 3-element array whose components
        are parameter name, parameter type, and parameter description). */
    private static final String[][] PARAMETER_INFO = new String[][] {
        { "TOPLEVEL", "string", "full path to toplevel executable binary" }
    };

    /**
     * {@inheritDoc}
     */
    @Override
    public String getAppletInfo() {
        return TopLevelApplet.APPLET_INFO;
    } // end method 'getAppletInfo()'

    /**
     * {@inheritDoc}
     */
    @Override
    public String[][] getParameterInfo() {
        return TopLevelApplet.PARAMETER_INFO;
    } // end method 'getParameterInfo()'

    /**
     * Looks up for the OCaml toplevel executable in the system path
     * as well as additional path given by "PATH" applet parameter and launch
     * it, interaction with the toplevel being done using a UI.
     */
    @Override
    public void init() {
        // constructs and launch interpreter
        final PipedInputStream in = new PipedInputStream();
        final PipedOutputStream out = new PipedOutputStream();
        final PipedOutputStream err = new PipedOutputStream();
        InOutPanel top = null;
        final String passedToplevel = getParameter("TOPLEVEL");
        final String toplevel = passedToplevel != null
            ? passedToplevel
            : "/usr/local/bin/ocaml";
        try {
            final ByteCodeParameters params =
                new ByteCodeParameters(new String[0],        /* arguments */
                                       false,                /* backtrace */
                                       false,                 /* stop JVM */
                                       in,                       /* input */
                                       new PrintStream(out,
                                                       true),   /* output */
                                       new PrintStream(err,
                                                       true),    /* error */
                                       false,                      /* awt */
                                       false,              /* bare canvas */
                                       false,              /* javax.sound */
                                       false,                     /* jdbm */
                                       "Unix",                      /* OS */
                                       false,           /* Unix emulation */
                                       toplevel,                  /* file */
                                       false,                 /* embedded */
                                       null,             /* embedded base */
                                       false,                   /* dialog */
                                       false,                     /* help */
                                       false,                  /* version */
                                       false,             /* disassembled */
                                       64 * 1024,      /* init stack size */
                                       64 * 1024,       /* max stack size */
                                       new String[0],        /* providers */
                                       true);      /* compile dispatchers */
            final RandomAccessInputStream source =
                new RandomAccessInputStream(getClass().getResourceAsStream(toplevel));
            final Interpreter interp =
                new Interpreter(params, new File("."), source);
            final ThreadedFileHook hook = new ThreadedFileHook();
            hook.start();
            interp.getContext().setFileHook(hook);
            top = new InOutPanel(interp,
                                 new PrintStream(new PipedOutputStream(in),
                                                 true),
                                 new PipedInputStream(out),
                                 new PipedInputStream(err));
        } catch (final Throwable t) {
            setLayout(new BorderLayout());
            add(new JLabel("error: " + t.toString(), SwingConstants.CENTER),
                BorderLayout.CENTER);
            return;
        } // end try/catch

        // ui construction
        setLayout(new BorderLayout());
        add(top, BorderLayout.CENTER);
    } // end method 'init()'

    /**
     * Data object modelling a file hook request.
     */
    private final class Request {

        /** Whether an url is requested. */
        private final boolean url;

        /** File name for URL / input stream. */
        private final String file;

        /**
         * Constructs a request.
         * @param u whether an URL is requested
         * @param f file name for URL / input stream
         */
        private Request(final boolean u, final String f) {
            assert f != null : "null f";
            this.url = u;
            this.file = f;
        } // end construtor (boolean, String)

    } // end inner-class 'Request'

    /**
     * Data object modelling a file hook response.
     */
    private final class Response {

        /** Returned input stream, if any. */
        private final InputStream inputStream;

        /** Returned url, if any. */
        private final URL url;

        /**
         * Constructs a response from input stream,
         * setting url to <tt>null</tt>.
         * @param i input stream to be returned
         */
        private Response(final InputStream i) {
            this.inputStream = i;
            this.url = null;
        } // end constructor(InputStream)

        /**
         * Constructs a response from URL,
         * setting input stream to <tt>null</tt>.
         * @param u URL to be returned
         */
        private Response(final URL u) {
            this.inputStream = null;
            this.url = u;
        } // end constructor(URL)

    } // end inner-class 'Response'

    /**
     * This class implements a worker thread that loads resources.
     */
    public final class ThreadedFileHook extends Thread implements FileHook {

        /** Queue for passing parameters from request method to thread. */
        private final BlockingQueue<AtomicReference<Request>> parameters =
            new LinkedBlockingQueue<AtomicReference<Request>>(1);

        /** Queue for passing results from thread to request method. */
        private final BlockingQueue<AtomicReference<Response>> results =
            new LinkedBlockingQueue<AtomicReference<Response>>(1);

        /**
         * {@inheritDoc}
         */
        @Override
        public void run() {
            try {
                while (true) {
                    final Request req = this.parameters.take().get();
                    if (req.url) {
                        this.results.put(new AtomicReference<Response>(new Response(getClass().getResource(req.file))));
                    } else {
                        this.results.put(new AtomicReference<Response>(new Response(getClass().getResourceAsStream(req.file))));
                    } // end if/else
                } // end while
            } catch (final InterruptedException ie) {
                // nothing to do
            } // end try/catch
        } // end method 'run()'

        /**
         * {@inheritDoc}
         */
        @Override
        public InputStream getInputStream(final String file) {
            try {
                this.parameters.put(new AtomicReference<Request>(new Request(false, file)));
                return this.results.take().get().inputStream;
            } catch (final InterruptedException ie) {
                return null;
            } // end try/catch
        } // end method 'getInputStream(String)'

        /**
         * {@inheritDoc}
         */
        @Override
        public URL getURL(final String file) {
            try {
                this.parameters.put(new AtomicReference<Request>(new Request(true, file)));
                return this.results.take().get().url;
            } catch (final InterruptedException ie) {
                return null;
            } // end try/catch
        } // end method 'getInputStream(String)'

    } // end inner-class 'ThreadedFileHook'

} // end class 'TopLevelApplet'
